package mirrormonkey.util;

import java.util.Collection;
import java.util.HashSet;

/**
 * Generates <tt>int</tt> values that are guaranteed to be unique.
 * 
 * @author Philipp Christian Loewner
 * 
 */
public class IdGenerator {

	/**
	 * If no first ID is supplied to the constructor, this is used as first ID.
	 */
	public static final int DEFAULT_FIRST_ID = 1;

	/**
	 * Contains every ID that is currently in use.
	 */
	private final Collection<Integer> reservedIds;

	/**
	 * Next ID to be generated (if not in use).
	 */
	private int nextId;

	/**
	 * Creates a new <tt>IdGenerator</tt> that will use
	 * <tt>DEFAULT_FIRST_ID</tt> as first returned ID.
	 */
	public IdGenerator() {
		this(DEFAULT_FIRST_ID);
	}

	/**
	 * Creates a new <tt>IdGenerator</tt>. The first generated ID will be equal
	 * to the supplied parameter.
	 * 
	 * @param firstId
	 *            first id to be returned by <tt>generateAndReserve</tt>
	 */
	public IdGenerator(int firstId) {
		nextId = firstId;
		reservedIds = new HashSet<Integer>();
	}

	public void setNextId(int nextId) {
		this.nextId = nextId;
	}

	/**
	 * Returns and reserves the next free ID. Next in this case means the next
	 * int greater than the value previously returned by this method that is
	 * still free or the smallest free ID if an overflow occurs when searching
	 * for the next free ID.
	 * 
	 * @return the next free ID
	 * @throws IllegalStateException
	 *             if there are no free IDs left
	 */
	public int generateAndReserve() {
		int guardian = nextId;
		while (reservedIds.contains(nextId)) {
			if (++nextId == guardian) {
				throw new IllegalStateException("All IDs taken");
			}
		}
		reserve(nextId);
		return nextId;
	}

	/**
	 * Indicates that an ID is no longer used by the user, freeing it for future
	 * calls of <tt>generateAndReserve</tt>.
	 * 
	 * @param id
	 *            the ID that is no longer in use
	 */
	public void release(int id) {
		reservedIds.remove(id);
	}

	/**
	 * Explicitly reserves a single ID. This ID will no longer be considered
	 * when searching for free IDs until it is released.
	 * 
	 * @param id
	 *            the ID to reserve
	 * @throws IllegalStateException
	 *             if <tt>id</tt> is already reserved
	 */
	public void reserve(int id) {
		if (reservedIds.contains(id)) {
			throw new IllegalStateException("ID already taken.");
		}
		reservedIds.add(id);
	}

}
